#include "NetworkManager.h"
#include "RakNetTypes.h"
#include "RakSleep.h"
#include "MessageIdentifiers.h"
#include "Logger.h"
#include "Exception.h"

using namespace game_engine;
using namespace game_engine::utils;
using namespace RakNet;

bool NetworkManager::instanceFlag = false;
NetworkManager* NetworkManager::instance = NULL;

const UINT16 NUM_PORT = 5000;
const UINT16 MAX_CONNEXIONS = 5;
const string BROADCAST_ADDRESS = "255.255.255.255";

///////////////////////////////////////////////////////////////////////////////
// Constructors and Destructors
///////////////////////////////////////////////////////////////////////////////

NetworkManager::~NetworkManager(void)
{
    if (instance)
    {
        instanceFlag = false;

        rakPeer->Shutdown(100, 0);
        RakNet::RakPeerInterface::DestroyInstance(rakPeer);
        delete rpc3Inst;
    }
}

///////////////////////////////////////////////////////////////////////////////
// Other public methods
///////////////////////////////////////////////////////////////////////////////

void NetworkManager::initialize(const BOOL isServer) throw(...)
{
    rpc3Inst->SetNetworkIDManager(&networkIDManager);

	if (isServer)
	{
		SocketDescriptor socketDescriptor(NUM_PORT, 0);
        // Only IPV4 supports broadcast on 255.255.255.255
		socketDescriptor.socketFamily = AF_INET; 
		StartupResult res = rakPeer->Startup(MAX_CONNEXIONS, &socketDescriptor, 1);
        if (res != RAKNET_STARTED)
        {
            string err("server startup failed");
            throw RuntimeException(err);
        }
		rakPeer->SetMaximumIncomingConnections(MAX_CONNEXIONS);
        Logger::INFO(GAMEENGINE_LOG, "Server started");
	}
	else
	{
		SocketDescriptor socketDescriptor(0, 0);
        // Only IPV4 supports broadcast on 255.255.255.255
		socketDescriptor.socketFamily = AF_INET; 
		StartupResult res = rakPeer->Startup(1, &socketDescriptor, 1);
        if (res != RAKNET_STARTED)
        {
            string err("client startup failed");
            throw RuntimeException(err);
        }

		// Send out a LAN broadcast to find other instances on the same computer
		if (!rakPeer->Ping(BROADCAST_ADDRESS.c_str(), NUM_PORT, true, 0))
        {
            string err("client ping failed");
            throw RuntimeException(err);
        }

		Logger::INFO(GAMEENGINE_LOG, "Client started. Will automatically connect to running servers");
	}

    rakPeer->AttachPlugin(rpc3Inst);

    // Block until connection had been established
    Packet *p;
    BOOL quitFor = false;
    string err;
    while(!quitFor)
    {
	    for (p=rakPeer->Receive(); p; rakPeer->DeallocatePacket(p), p=rakPeer->Receive())
	    {
		    switch (p->data[0])
		    {
		    case ID_DISCONNECTION_NOTIFICATION:
			    err = "ID_DISCONNECTION_NOTIFICATION";
                quitFor = true;
			    break;
		    case ID_ALREADY_CONNECTED:
			    err = "ID_ALREADY_CONNECTED";
                quitFor = true;
			    break;
		    case ID_CONNECTION_ATTEMPT_FAILED:
			    err = "ID_CONNECTION_ATTEMPT_FAILED";
                quitFor = true;
			    break;
		    case ID_NO_FREE_INCOMING_CONNECTIONS:
			    err = "ID_NO_FREE_INCOMING_CONNECTIONS";
                quitFor = true;
			    break;
		    case ID_UNCONNECTED_PONG:
			    // Found the server
                Logger::INFO(GAMEENGINE_LOG, "Server found. Attempt to connect ...");
			    rakPeer->Connect(p->systemAddress.ToString(false),p->systemAddress.GetPort(),0,0,0);
			    break;
		    case ID_CONNECTION_REQUEST_ACCEPTED:
			    // This tells the client they have connected
                Logger::INFO(GAMEENGINE_LOG, "Client has connected to server");
                quitFor = true;
			    break;
		    case ID_NEW_INCOMING_CONNECTION:
		    {
                Logger::INFO(GAMEENGINE_LOG, "Server has received a request to connect");
                quitFor = true;
			    break;
		    }				
		    case ID_RPC_REMOTE_ERROR:
		    {
			    // Recipient system returned an error
			    err = "ID_RPC_REMOTE_ERROR";
                quitFor = true;
                break;
		    }
		    }  // switch

	    }  // for

        // Sleep during 100 ms
        RakSleep(100);

    }  // while

    // Check for errors
    if (!err.empty())
    {
        Logger::ERR(GAMEENGINE_LOG, string("Initialization of network manager failed : ") + err);
        throw RuntimeException(err);
    }
}

void NetworkManager::update(void) throw(...)
{
    // Read pending packets from network. This must be done in order to receive
    // the RPC
    Packet *p = NULL;
    for (p=rakPeer->Receive(); p; rakPeer->DeallocatePacket(p), p=rakPeer->Receive())
    {
        Logger::INFO(GAMEENGINE_LOG, "Packet received in update");
    }  // for
}

///////////////////////////////////////////////////////////////////////////////
// Private methods
///////////////////////////////////////////////////////////////////////////////

NetworkManager::NetworkManager(void)
    : rakPeer(NULL), rpc3Inst(NULL)
{
    rakPeer = RakPeerInterface::GetInstance();
    rpc3Inst = new RakNet::RPC3;
}

///////////////////////////////////////////////////////////////////////////////
// Getters
///////////////////////////////////////////////////////////////////////////////

NetworkManager* NetworkManager::getInstance(void)
{
    if(!instanceFlag)
    {
        instance = new NetworkManager();
        instanceFlag = true;
        return instance;
    }
    else
    {
        return instance;
    }
}

NetworkIDManager& NetworkManager::getNetworkIDManager(void)
{
    return networkIDManager;
}

RPC3* NetworkManager::getRpc3Inst(void)
{
    return rpc3Inst;
}
